package cn.lg.soar.flow.biz.service.impl;

import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.common.util.ParameterUtil;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.flow.api.enums.AuditTypeEnum;
import cn.lg.soar.flow.api.enums.NodeStatusEnum;
import cn.lg.soar.flow.api.enums.RejectTaskEnum;
import cn.lg.soar.flow.api.model.*;
import cn.lg.soar.flow.api.model.node.ApprovalNode;
import cn.lg.soar.flow.api.model.node.Node;
import cn.lg.soar.flow.api.utils.ProcessUtils;
import cn.lg.soar.flow.biz.entity.SoarAuditLog;
import cn.lg.soar.flow.biz.entity.SoarProcessVersion;
import cn.lg.soar.flow.biz.mapper.FlowInstanceMapper;
import cn.lg.soar.flow.biz.model.*;
import cn.lg.soar.flow.biz.service.FlowInstanceService;
import cn.lg.soar.flow.biz.service.SoarAuditLogService;
import cn.lg.soar.flow.biz.service.SoarProcessVersionService;
import cn.lg.soar.system.api.service.IPostApi;
import cn.lg.soar.system.api.service.IUserApi;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 节点审批人
 * @author luguoxiang
 * @since 2025-08-07
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Slf4j
@Service
public class FlowInstanceServiceImpl implements FlowInstanceService {

    @Autowired
    private FlowInstanceMapper mapper;
    @Autowired
    private SoarProcessVersionService processVersionService;
    @Autowired
    private SoarAuditLogService auditLogService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private IPostApi postApi;
    @Autowired
    private IUserApi userApi;

    @Override
    public List<String> getApprovers(Long userId) {
        List<Long> postIds = postApi.idsByUserId(userId);
        List<String> approvers = postIds.stream().map(ProcessUtils::toPostApproval).collect(Collectors.toList());
        approvers.add(ProcessUtils.toUserApproval(userId));
        return approvers;
    }

    @Override
    public IPage<InstanceVO> page(IPage<InstanceVO> page, InstanceQuery query) {
        page = mapper.page(page, query);
        List<InstanceVO> records = page.getRecords();
        if (!records.isEmpty()) {
            List<Long> userIds = records.stream().map(InstanceVO::getStartUserId).distinct().toList();
            Map<Long, String> nameMapper = userApi.nameMapper(userIds);
            for (InstanceVO record : records) {
                record.setStartUserName(nameMapper.get(record.getStartUserId()));
            }
        }
        return page;
    }

    @Override
    public IPage<InstanceVO> started(IPage<InstanceVO> page, InstanceQuery query) {
        page = mapper.started(page, query);
        List<InstanceVO> records = page.getRecords();
        if (!records.isEmpty()) {
            List<Long> userIds = records.stream().map(InstanceVO::getStartUserId).distinct().toList();
            Map<Long, String> nameMapper = userApi.nameMapper(userIds);
            for (InstanceVO record : records) {
                record.setStartUserName(nameMapper.get(record.getStartUserId()));
            }
        }
        return page;
    }

    @Override
    public IPage<TodoDTO> todo(IPage<TodoDTO> page, Long userId, TodoQuery query) {
        page = mapper.todo(page, getApprovers(userId), query);
        List<TodoDTO> records = page.getRecords();
        if (!records.isEmpty()) {
            List<Long> userIds = records.stream().map(TodoDTO::getStartUserId).distinct().toList();
            Map<Long, String> nameMapper = userApi.nameMapper(userIds);
            for (TodoDTO record : records) {
                record.setStartUserName(nameMapper.get(record.getStartUserId()));
            }
        }
        return page;
    }

    @Override
    public IPage<DoneDTO> done(IPage<DoneDTO> page, Long userId, DoneQuery query) {
        page = mapper.done(page, ProcessUtils.toUserApproval(userId), query);
        List<DoneDTO> records = page.getRecords();
        if (!records.isEmpty()) {
            Set<Long> userIds = records.stream().map(DoneDTO::getStartUserId).collect(Collectors.toSet());
            Map<Long, String> nameMapper = userApi.nameMapper(userIds);
            for (DoneDTO record : records) {
                record.setStartUserName(nameMapper.get(record.getStartUserId()));
            }
        }
        return page;
    }

    @Override
    public List<ApprovalTaskDTO> approvalTasks(Long userId, String procInstId, String taskKey) {
        return mapper.approvalTasks(procInstId, getApprovers(userId), taskKey);
    }

    @Override
    public ProgressVO progress(String procInstId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(procInstId)
                .singleResult();
        SoarProcessVersion process = processVersionService.getById(historicProcessInstance.getProcessDefinitionId());
        List<AuditLogDTO> logList = auditLogService.list(procInstId);
        List<Node> nodes = process.getNodes();
        Map<String, List<AuditLogDTO>> group = logList.stream().collect(Collectors.groupingBy(AuditLogDTO::getTaskKey));
        boolean ended = historicProcessInstance.getEndTime() != null;
        ProcessUtils.forEach(nodes, x -> {
            x.setStatus(ended && "end".equals(x.getType()) ? NodeStatusEnum.PASS : NodeStatusEnum.PENDING);
            List<AuditLogDTO> list = group.get(x.getKey());
            if (DataUtil.isValuable(list)) {
                int pass = 0;
                int reject = 0;
                for (AuditLogDTO auditLogDTO : list) {
                    if (AuditTypeEnum.pass.equals(auditLogDTO.getResult())) {
                        pass++;
                    } else if (auditLogDTO.getResult() != null) {
                        reject++;
                    }
                }
                int size = list.size();
                if (pass == size) {
                    x.setStatus(NodeStatusEnum.PASS);
                } else if (reject != 0) {
                    x.setStatus(NodeStatusEnum.REJECT);
                } else {
                    x.setStatus(NodeStatusEnum.RUNNING);
                }
            } else {
                x.setStatus(NodeStatusEnum.PENDING);
            }
        });
        ProgressVO vo = new ProgressVO();
        vo.setNodes(nodes);
        vo.setLogs(logList);
        return vo;
    }

    @Override
    public void passTask(PassTaskDTO passTaskDTO) {
        Long userId = passTaskDTO.getUserId();
        List<ApprovalTaskDTO> tasks = passTaskDTO.getTasks();
        String comments = passTaskDTO.getComments();
        Map<String, Object> variables = passTaskDTO.getVariables();
        String userApproval = ProcessUtils.toUserApproval(userId);

        List<SoarAuditLog> logs = new ArrayList<>(tasks.size());
        for (ApprovalTaskDTO task : tasks) {
            String taskId = task.getTaskId();
            taskService.complete(taskId, userApproval, variables);
            // 添加审批日志
            SoarAuditLog auditLog = new SoarAuditLog();
            auditLog.setTaskId(taskId);
            auditLog.setUserId(userId);
            auditLog.setResult(AuditTypeEnum.pass);
            auditLog.setComments(comments);
            logs.add(auditLog);
            log.info("任务已完成: {}", taskId);
        }
        auditLogService.saveBatch(logs);
    }

    @Override
    public void rejectTask(RejectTaskDTO rejectTaskDTO) {
        Long userId = rejectTaskDTO.getUserId();
        List<ApprovalTaskDTO> tasks = rejectTaskDTO.getTasks();
        String comments = rejectTaskDTO.getComments();
        String procInstId = rejectTaskDTO.getProcInstId();
        Map<String, Object> variables = rejectTaskDTO.getVariables();
        String backNodeKey = rejectTaskDTO.getBackNodeKey();
        String userApproval = ProcessUtils.toUserApproval(userId);
        // 驳回处理
        List<SoarAuditLog> logs = new ArrayList<>(tasks.size());
        for (ApprovalTaskDTO task : tasks) {
            String taskId = task.getTaskId();
            String taskKey = task.getTaskKey();
            String processId = task.getProcessId();
            Map<String, ApprovalNode> nodeMap = processVersionService.getNodeMap(processId);
            ApprovalNode node = nodeMap.get(taskKey);
            RejectTaskEnum rejectTask = node.getBackType();
            // 任务处理人
            taskService.setAssignee(taskId, userApproval);
            taskService.setVariables(taskId, variables);
            // 处理驳回方式
            switch (rejectTask) {
                case first:
                    // 退回发起人节点
                    BpmnModel bpmnModel = repositoryService.getBpmnModel(processId);
                    Optional<UserTask> firstTask = bpmnModel.getMainProcess().getFlowElements().stream()
                            .filter(e -> e instanceof UserTask)
                            .map(e -> (UserTask) e)
                            .findFirst();
                    if (firstTask.isPresent()) {
                        // 返回第一个节点
                        jumpToTask(procInstId, taskKey, firstTask.get().getId());
                    } else {
                        // 结束流程
                        jumpToTask(procInstId, taskKey, "end");
                        log.warn("未找到第一个节点，流程结束：procInstId={}，taskKey = {}", procInstId, taskKey);
                    }
                    break;
                case prev:
                    // 退回上一个节点
                    List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                            .processInstanceId(procInstId)
                            .finished() // 只查已完成的任务（当前任务可能还在运行，不查它）
                            .orderByHistoricTaskInstanceEndTime()
                            .asc()
                            .list();
                    // 2. 构建“去重路径”：只保留每个节点第一次出现的记录
                    List<String> uniquePath = new ArrayList<>();
                    Set<String> seenTaskKeys = new HashSet<>();
                    for (HistoricTaskInstance x : list) {
                        String taskDefKey = x.getTaskDefinitionKey();
                        if (!seenTaskKeys.contains(taskDefKey)) {
                            seenTaskKeys.add(taskDefKey);
                            uniquePath.add(taskDefKey);
                        }
                    }
                    // 3. 找到上一个任务在“去重路径”中的位置
                    int prevIndex = -1;
                    for (int i = 0; i < uniquePath.size(); i++) {
                        if (taskKey.equals(uniquePath.get(i))) {
                            prevIndex = i - 1;
                            break;
                        }
                    }
                    // 如果当前节点不在去重路径中？说明是第一次审批，或者路径外节点 → 按正常历史找上一个
                    if (prevIndex == -1) {
                        prevIndex = uniquePath.size() - 1;
                    }
                    if(prevIndex == -1) {
                        // 结束流程
                        jumpToTask(procInstId, taskKey, "end");
                        log.warn("未找到上一个节点，流程结束：procInstId={}，taskKey = {}", procInstId, taskKey);
                    } else {
                        // 返回上一个节点
                        jumpToTask(procInstId, taskKey, uniquePath.get(prevIndex));
                    }
                    break;
                case assign:
                    // 退回指定节点
                    AssertUtil.notEmpty(node.getBackNode(), "未找到指定审批节点");
                    jumpToTask(procInstId, taskKey, node.getBackNode());
                    break;
                case selected:
                    // 退回审批人自选节点
                    ParameterUtil.notEmpty(backNodeKey, "请指定退回的节点");
                    jumpToTask(procInstId, taskKey, backNodeKey);
                    break;
                case end:
                    // 结束流程
                    jumpToTask(procInstId, taskKey, "end");
                    break;
                default:
                    throw new UnsupportedOperationException("暂不支持的驳回类型: " + rejectTask.name());
            }
            // 添加审批日志
            SoarAuditLog auditLog = new SoarAuditLog();
            auditLog.setTaskId(taskId);
            auditLog.setUserId(userId);
            auditLog.setResult(AuditTypeEnum.reject);
            auditLog.setComments(comments);
            logs.add(auditLog);
            log.info("任务不通过: {}", taskId);
        }
        auditLogService.saveBatch(logs);
    }

    /**
     * 通用跳转方法（使用 Flowable 的跳转工具类）
     * 注意：需引入 flowable-spring-boot-starter 或自定义跳转逻辑
     */
    private void jumpToTask(String procInstId, String sourceKey, String targetKey) {
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(procInstId)
                .moveActivityIdTo(sourceKey, targetKey)
                .changeState();
    }

}
